/*
Copyright 2020 Lucas Heitzmann Gabrielli.
This file is part of gdstk, distributed under the terms of the
Boost Software License - Version 1.0.  See the accompanying
LICENSE file or <http://www.boost.org/LICENSE_1_0.txt>
*/

#ifndef QLIB_HEADER_UTILS
#define QLIB_HEADER_UTILS

#define __STDC_FORMAT_MACROS 1
#define _USE_MATH_DEFINES

#define GDSTK_PRINT_BUFFER_COUNT 1024
#define GDSTK_DOUBLE_BUFFER_COUNT 1024

#define GDSTK_MIN_POINTS 4

#define GDSTK_PARALLEL_EPS 1e-8

#define GDSTK_MAP_GROWTH_FACTOR 2
#define GDSTK_INITIAL_MAP_CAPACITY 8
#define GDSTK_MAP_CAPACITY_THRESHOLD 5  // in tenths

#define COUNT(a) (sizeof(a) / sizeof(0 [a]))

// Linear interpoaltion
#define LERP(a, b, u) ((a) * (1 - (u)) + (b) * (u))

// Smooth interpolation (3rd order polynomial with zero derivatives at 0 and 1)
#define SERP(a, b, u) ((a) + ((b) - (a)) * (3 - 2 * (u)) * (u) * (u))

#ifdef NDEBUG
#define DEBUG_HERE ((void)0)
#define DEBUG_PRINT(...) ((void)0)
#else
#define DEBUG_HERE                                                     \
  do {                                                                 \
    fprintf(error_logger, "%s:%d:%s\n", __FILE__, __LINE__, __func__); \
    fflush(error_logger);                                              \
  } while (false)
#define DEBUG_PRINT(...)                \
  do {                                  \
    fprintf(error_logger, __VA_ARGS__); \
    fflush(error_logger);               \
  } while (false)
#endif

// From http://esr.ibiblio.org/?p=5095
#define IS_BIG_ENDIAN (*(uint16_t*)"\0\xFF" < 0x100)

#ifdef _WIN32
#define FSEEK64 _fseeki64
#else
// Assuming sizeof(long) == 8
#define FSEEK64 fseek
#endif

#include <math.h>
#include <stdint.h>
#include <time.h>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <cfloat>
#include <cmath>

// #include "array.hpp"
// #include "vec.hpp"
#include "qlib/core/array.h"

namespace qlib {
// Error codes
// enum struct ErrorCode {
//   NoError = 0,
//   // Warnings
//   BooleanError,
//   IntersectionNotFound,
//   MissingReference,
//   UnsupportedRecord,
//   UnofficialSpecification,
//   InvalidRepetition,
//   Overflow,
//   // Errors
//   ChecksumError,
//   OutputFileOpenError,
//   InputFileOpenError,
//   InputFileError,
//   FileError,
//   InvalidFile,
//   InsufficientMemory,
//   ZlibError,
// };

// Tag encapsulates layer and data (text) type.  The implementation details
// might change in the future.  The only guarantee is that a zeroed Tag
// indicates layer 0 and type 0.
// typedef uint64_t Tag;
// inline Tag make_tag(uint32_t layer, uint32_t type) {
//   return ((uint64_t)type << 32) | (uint64_t)layer;
// };
// inline uint32_t get_layer(Tag tag) {
//   return (uint32_t)tag;
// };
// inline uint32_t get_type(Tag tag) {
//   return (uint32_t)(tag >> 32);
// };
// inline void set_layer(Tag& tag, uint32_t layer) {
//   tag = make_tag(layer, get_type(tag));
// };
// inline void set_type(Tag& tag, uint32_t type) {
//   tag = make_tag(get_layer(tag), type);
// };

struct Tag {
  uint32_t layer{0};
  uint32_t type{0};
  struct Hash {
    std::size_t operator()(const Tag& id) const { return std::hash<uint32_t>()(id.layer ^ (id.type << 16)); }
  };
  bool operator==(const Tag& rhs) const { return layer == rhs.layer && type == rhs.type; }
  bool operator!=(const Tag& rhs) const { return !(*this == rhs); }
  bool operator<(const Tag& rhs) const { return layer < rhs.layer; }

  Tag() {};
  Tag(uint32_t layer_, uint32_t type_) {
    layer = layer_;
    type = type_;
  }
};

// // Argument between 0 and 1, plus user data
// typedef double (*ParametricDouble)(double, void*);

// // Argument between 0 and 1, plus user data
// typedef Vec2d (*ParametricVec2)(double, void*);

// // Arguments: first_point, first_direction, second_point, second_direction,
// // user data
// typedef Array<Vec2d> (*EndFunction)(const Vec2d, const Vec2d, const Vec2d, const Vec2d, void*);

// // Arguments: first_point, first_direction, second_point, second_direction,
// // center, width, user data
// typedef Array<Vec2d> (*JoinFunction)(const Vec2d, const Vec2d, const Vec2d, const Vec2d, const Vec2d, double, void*);

// // Arguments: radius, initial_angle, final_angle, center, user data
// typedef Array<Vec2d> (*BendFunction)(double, double, double, const Vec2d, void*);

// Returns new dynamically allocated memory.  If len if not NULL, it is set to
// the length of the string (including the null termination).
char* copy_string(const char* str, uint64_t* len);

// If true, m is set to the multiplicative factor, i.e., angle = 0.5 * M_PI * m
bool is_multiple_of_pi_over_2(double angle, int64_t& m);

// Number of points needed to approximate an arc within some tolerance
uint64_t arc_num_points(double angle, double radius, double tolerance);

double elliptical_angle_transform(double angle, double radius_x, double radius_y);

// Distance squared from p to the line defined by p1 and p2
double distance_to_line_sq(const Vec2d p, const Vec2d p1, const Vec2d p2);

// Distance from p to the line defined by p1 and p2
double distance_to_line(const Vec2d p, const Vec2d p1, const Vec2d p2);

// Finds the intersection between paths defined by point p0 and direction ut0
// (unit vector along the line) and by point p1 and direction ut1.  Scalars u0
// and u1 can be used to determine the intersection point:
// p = p0 + u0 * ut0 == p1 + u1 * ut1
void segments_intersection(const Vec2d p0, const Vec2d ut0, const Vec2d p1, const Vec2d ut1, double& u0, double& u1);

void scale_and_round_array(const Vec2dArray points, double scaling, Vec2iArray& scaled_points);

// Swap to big-endian (do nothing if the host is big-endian)
void big_endian_swap16(uint16_t* buffer, uint64_t n);
void big_endian_swap32(uint32_t* buffer, uint64_t n);
void big_endian_swap64(uint64_t* buffer, uint64_t n);

// Swap to little-endian (do nothing if the host is little-endian)
void little_endian_swap16(uint16_t* buffer, uint64_t n);
void little_endian_swap32(uint32_t* buffer, uint64_t n);
void little_endian_swap64(uint64_t* buffer, uint64_t n);

// Update the checksum32 of checksum with count values from bytes
uint32_t checksum32(uint32_t checksum, const uint8_t* bytes, uint64_t count);

Vec2d eval_line(double t, const Vec2d p0, const Vec2d p1);

// Quadratic Bézier defined by control points p0, p1 and p2 at 0 ≤ t ≤ 1
Vec2d eval_bezier2(double t, const Vec2d p0, const Vec2d p1, const Vec2d p2);

// Cubic Bézier defined by control points p0 through p3 at 0 ≤ t ≤ 1
Vec2d eval_bezier3(double t, const Vec2d p0, const Vec2d p1, const Vec2d p2, const Vec2d p3);

// Evaluate a Bézier curve defined by count control points at 0 ≤ t ≤ 1
Vec2d eval_bezier(double t, const Vec2d* ctrl, uint64_t count);

// Calculates the control points for a smooth cubic Bézier interpolation
// following:
//
// John D. Hobby. “Smooth, easy to compute interpolating splines.” Discrete
// Comput. Geom., 1:123–140, 1986.
//
// Calculated control points ca and cb are stored in points, which must have
// the appropriate count and layout:
//
// points[3 * count] = {p[0], ca[0], cb[0],
//                      p[1], ca[1], cb[1],
//                      …,
//                      p[count - 1], ca[count - 1], cb[count - 1]};
//
// The last controls are only present if cycle == true.  Parameter angles can
// be used to constrain the angle at any interpolation point by setting the
// respective angle_constraints to true.  Defaults for tension (at each
// interpolation point) and curl should be 1.
void hobby_interpolation(uint64_t count, Vec2d* points, double* angles, bool* angle_constraints, Vec2d* tension, double initial_curl,
                         double final_curl, bool cycle);

// Stores the convex hull of points into result
void convex_hull(const Vec2dArray points, Vec2dArray& result);

// Return a global buffer with the representation of the value in fixed format
// with a maximal precision set.  This function is meant for internal use only.
char* double_print(double value, uint32_t precision, char* buffer, size_t buffer_size);

// Returns the default SVG style for a given tag.  The return value points to a
// statically allocated buffer that is overwritten in future calls to this
// function.
const char* default_svg_shape_style(Tag tag);
const char* default_svg_label_style(Tag tag);

// Thread-safe version of localtime.
tm* get_now(tm& result);

// FNV-1a hash function (64 bits)
#define HASH_FNV_PRIME 0x00000100000001b3
#define HASH_FNV_OFFSET 0xcbf29ce484222325
template <class T>
inline uint64_t hash(T key) {
  uint64_t result = HASH_FNV_OFFSET;
  uint8_t* byte = (uint8_t*)(&key);
  for (unsigned i = sizeof(T); i > 0; i--) {
    result ^= *byte++;
    result *= HASH_FNV_PRIME;
  }
  return result;
}

inline uint64_t hash(const char* key) {
  uint64_t result = HASH_FNV_OFFSET;
  for (const char* c = key; *c; c++) {
    result ^= (uint64_t)(*c);
    result *= HASH_FNV_PRIME;
  }
  return result;
}

extern FILE* error_logger;
void set_error_logger(FILE* log);
}  // namespace qlib

// my utils
namespace qlib {
template <typename T>
inline void vector_free_space(std::vector<T>& vector) {
  std::vector<T>().swap(vector);
}

template <typename T>
inline void vector_reserve_now(std::vector<T>& vector, uint64_t add_size) {
  vector.reserve(vector.size() + add_size);
}

template <typename T>
inline int vector_resize_now(std::vector<T>& vector, uint64_t add_size) {
  int old_size = vector.size();
  vector.resize(vector.size() + add_size);
  return old_size;
}

inline Vec2d box_center(const Vec2dArray& box) {
  return {(box[0].x + box[1].x) / 2.0, (box[0].y + box[1].y) / 2.0};
}

inline Vec2dArray box_to_points(const Vec2dArray& box) {
  Vec2dArray res = {};
  auto& pos = box[0];
  auto w = box[1].x - box[0].x;
  auto h = box[1].y - box[0].y;
  res.push_back(pos);
  res.push_back(pos + Vec2d(w, 0));
  res.push_back(pos + Vec2d(w, h));
  res.push_back(pos + Vec2d(0, h));
  res.push_back(pos);
  return res;
}

inline Vec2dArray get_box(const Vec2dArray& points) {
  Vec2dArray res(2);
  Vec2d& min = res[0];
  Vec2d& max = res[1];
  if (points.size() == 0) {
    min = {0, 0};
    max = {0, 0};
  }
  min.x = min.y = DBL_MAX;
  max.x = max.y = -DBL_MAX;
  for (auto& p : points) {
    if (p.x < min.x)
      min.x = p.x;
    if (p.x > max.x)
      max.x = p.x;
    if (p.y < min.y)
      min.y = p.y;
    if (p.y > max.y)
      max.y = p.y;
  }
  return res;
}

inline Vec2dArray connect_end(const Vec2dArray& points) {
  auto p = points;
  p.push_back(points[0]);
  return p;
}

inline Vec2dArray get_edge_mid_points(const Vec2dArray& points) {
  Vec2dArray res = {};
  for (uint32_t i = 0; i < points.size() - 1; i++) {
    auto p = (points[i] + points[i + 1]) / 2.0f;  // 取两点坐标的平均值
    res.push_back(p);
  }
  return res;
}

inline std::vector<Vec2dArray> get_edges(const Vec2dArray& points) {
  std::vector<Vec2dArray> edges = {};
  for (uint32_t i = 0; i < points.size() - 1; i++) {
    edges.push_back(Vec2dArray{points[i], points[i + 1]});
  }
  return edges;
}

inline Vec2dArray get_edges_mid(const std::vector<Vec2dArray>& edges) {
  Vec2dArray res = {};
  for (auto& edge : edges) {
    auto p = (edge[0] + edge[1]) / 2.0f;  // 取两点坐标的平均值
    res.push_back(p);
  }
  return res;
}

inline std::vector<std::string> split_str(const std::string& input, char ch) {
  std::vector<std::string> tokens;
  std::stringstream ss(input);
  std::string token;
  while (std::getline(ss, token, ch)) {
    // 可选：去除首尾空格（根据需要）
    if (!token.empty()) {
      tokens.push_back(token);
    }
  }
  return tokens;
}

inline std::string format_digits(double number, uint8_t precision) {
  std::ostringstream oss;
  oss << std::fixed << std::setprecision(precision) << number;
  std::string result = oss.str();
  // 去除小数点后的末尾零
  size_t dotPos = result.find('.');
  if (dotPos != std::string::npos) {
    size_t end = result.length() - 1;
    while (end > dotPos && result[end] == '0') {
      end--;
    }
    // 如果小数点后都是零，去掉小数点
    if (end == dotPos) {
      result = result.substr(0, dotPos);
    } else {
      result = result.substr(0, end + 1);
    }
  }
  return result;
}

template <typename T>
T Abs(T a) {
  return a >= 0 ? a : -a;
}

template <typename T>
T Min(T a, T b) {
  return a >= b ? b : a;
}

}  // namespace qlib

#endif
